<template>
  <div class="player-box">
    <div class="avatar-box">
      <LoadingOutlined :class="['wait', winner === '' && activePlayer === 'front' && 'active']" />
      <UserAddOutlined class="avatar" />
    </div>

    <a-button type="primary" :loading="loading" @click="matchPlayer">{{ btnName }}</a-button>

    <div class="avatar-box">
      <LoadingOutlined :class="['wait', winner === '' && activePlayer === 'back' && 'active']" />
      <UserDeleteOutlined class="avatar" />
    </div>
  </div>

  <div style="margin-bottom: 10px">
    <div v-if="winner">{{ `${winner === "front" ? "黑子" : "绿子"}赢!` }}</div>
    <div v-else-if="playerRole">
      给我分配的是
      <span class="text-highlight" :style="{ color: playerRole === 'front' ? FrontChessColor : BackChessColor }">{{
        playerRole === "front" ? "黑棋" : "绿棋"
      }}</span>
      ，现在该
      <span class="text-highlight" :style="{ color: activePlayer === 'back' ? BackChessColor : FrontChessColor }">{{
        activePlayer === "front" ? "黑棋" : "绿棋"
      }}</span>
      走了
    </div>
  </div>

  <div id="js-chessboard" class="chess-board">
    <div v-for="(chess, index) in board" class="chess-grid" :key="index">
      <!-- {{ index }} -->
      <div @click="placeChess(index)" :class="['chess-cell', chess]"></div>
    </div>
  </div>
  <div id="js-follow-mouse-chess" class="follow-mouse"></div>
</template>

<script setup>
import { ref, computed, onBeforeMount, onBeforeUnmount } from "vue";
import { message } from "ant-design-vue";
import { LoadingOutlined, UserAddOutlined, UserDeleteOutlined } from "@ant-design/icons-vue";
import myWebSocket from "./websocket";

const loading = ref(false);
const btnName = ref("匹配玩家");

// 给当前玩家分配的角色,初始为空
const playerRole = ref("");
// 轮到落子的一方
const activePlayer = ref("");
// 获胜方
const winner = ref("");

// 棋盘的行数和列数-因为棋盘的最后一行和一列会被隐藏,所以假设BOARD_SIZE=16时,实际的棋盘大小是15*15=225
const Board_Size = 16;
const FrontChessColor = "black";
const BackChessColor = "#52c41a";

// 初始化棋盘数组,没放置黑白棋子时值初始化为空字符串
const board = ref(Array(Board_Size * Board_Size).fill(""));

// websocket实例
let myWs = {};
onBeforeUnmount(() => {
  myWs?.close?.();
});

// 放置棋子
const placeChess = (index) => {
  // 放置条件--正在下棋的过程中+该点位未落过棋子+轮到己方落子
  if (winner.value === "" && board.value[index] === "" && playerRole.value === activePlayer.value) {
    // 设置落子位置
    board.value[index] = activePlayer.value;
    followMouseChess?.classList.remove("show");

    // 同步棋盘信息给服务器
    myWs.send({ type: "board", content: board.value, role: activePlayer.value });
    // 检查某方是否获胜
    checkWinner(index);
  }
};

// 设置棋盘上鼠标移动时棋子跟随效果
let followMouseChess;
const setMouseFollowChess = () => {
  const chessBoardArea = document.getElementById("js-chessboard");
  const rect = chessBoardArea.getBoundingClientRect();
  followMouseChess = document.getElementById("js-follow-mouse-chess");
  // 添加鼠标移动事件监听器，限定在棋盘区域内
  document.addEventListener("mousemove", function (event) {
    // 还未分配角色 || 该对方走棋了 || 或者某方获胜了
    if (playerRole.value === "" || winner.value || playerRole.value !== activePlayer.value) return;
    // 检查鼠标是否在棋盘区域内
    if (
      event.clientX >= rect.left &&
      event.clientX <= rect.right &&
      event.clientY >= rect.top &&
      event.clientY <= rect.bottom
    ) {
      // 更新自定义形状的位置
      followMouseChess?.classList.add("show");
      followMouseChess.style.left = `${event.clientX - 10}px`; // 减去一半宽度以居中
      followMouseChess.style.top = `${event.clientY - 10}px`; // 减去一半高度以居中
      followMouseChess.style.backgroundColor = playerRole.value === "front" ? FrontChessColor : BackChessColor;
    } else {
      // 如果鼠标移出棋盘区域，隐藏跟随鼠标棋子
      followMouseChess?.classList.remove("show");
    }
  });
};

// 检测正在下棋的一方是否获胜
const checkWinner = (index) => {
  const player = board.value[index];
  // 计算所放棋子所在的行列索引
  const row = Math.floor(index / Board_Size);
  const col = index % Board_Size;

  // 检查落子点所在行是否存在五子连珠
  const rowLine = board.value.slice(row * Board_Size, row * Board_Size + Board_Size);

  if (checkFiveChessConnected(rowLine, player)) {
    winner.value = player;
    myWs.send({ type: "winner", winner: player });
    return;
  }

  // 检查落子点所在列是否存在五子连珠
  const colLine = [];
  for (let i = 0; i < Board_Size; i++) {
    colLine.push(board.value[i * Board_Size + col]);
  }

  if (checkFiveChessConnected(colLine, player)) {
    winner.value = player;
    myWs.send({ type: "winner", winner: player });
    return;
  }

  // 检查落子点所在对角线是否存在五子连珠
  const diagonalIndex = calcDiagonalIndex(row, col, index, Board_Size);
  // console.log(diagonalIndex);

  // 检查左上右下对角线是否存在五子连珠
  if (
    checkFiveChessConnected(
      diagonalIndex.left.map((itemIndex) => board.value[itemIndex]),
      player
    )
  ) {
    winner.value = player;
    myWs.send({ type: "winner", winner: player });
    return;
  }

  // console.log(index, diagonalIndex, player);

  // 检查右上左下对角线是否存在五子连珠
  if (
    checkFiveChessConnected(
      diagonalIndex.right.map((itemIndex) => board.value[itemIndex]),
      player
    )
  ) {
    winner.value = player;
    myWs.send({ type: "winner", winner: player });
    return;
  }

  // 平局检测-因为黑子先下有优势,如何棋子落满棋盘,仍是平局,判断白子胜
  if (board.value.includes("") === false) {
    winner.value = "back";
    myWs.send({ type: "winner", winner: "back" });
  }
};

// 检查某个方向是否出现五子连珠
const checkFiveChessConnected = (line, player) => {
  if (line.length < 5) return false;

  for (let i = 0; i < line.length - 4; i++) {
    // console.log("checkFiveChessConnected", line.slice(i, i + 5));
    if (line.slice(i, i + 5).every((cell) => cell === player)) {
      return true;
    }
  }
  return false;
};

/**
 * 计算落子点对角线索引
 * @param {落子点的行号} row
 * @param {落子点的列号} col
 * @param {落子点在棋盘上的索引} placeIndex
 * @param {棋盘规格} boardSize
 */
function calcDiagonalIndex(row, col, placeIndex, boardSize) {
  /**
   * 计算某个基准点的左上，右上，左下，右下对角线上的落子点索引
   * @param {对角线计算基准点的行号} startRow
   * @param {对角线计算基准点的列号} startCol
   * @param {行的自增方向} directionRow
   * @param {列的自增方向} directionCol
   * @param {棋盘规格} boardSize
   */
  const getDiagIndex = (startRow, startCol, directionRow, directionCol, boardSize) => {
    const indexArr = [];
    let curRow = startRow + directionRow;
    let curCol = startCol + directionCol;

    while (curRow >= 0 && curCol >= 0 && curRow < boardSize && curCol < boardSize) {
      indexArr.push(curRow * boardSize + curCol);
      curRow += directionRow;
      curCol += directionCol;
    }

    return indexArr;
  };

  // 左上对角线落子点
  const leftTopDiag = getDiagIndex(row, col, -1, -1, boardSize);
  // 右下对角线落子点
  const rightBottomDiag = getDiagIndex(row, col, 1, 1, boardSize);

  // 右上对角线落子点
  const rightTopDiag = getDiagIndex(row, col, -1, 1, boardSize);

  // 左下对角线落子点
  const leftBottomDiag = getDiagIndex(row, col, 1, -1, boardSize);

  return {
    left: [...leftTopDiag.reverse(), placeIndex, ...rightBottomDiag],
    right: [...rightTopDiag.reverse(), placeIndex, ...leftBottomDiag],
  };
}

// 弹出获胜消息
const showWinnerMsg = (player) => {
  message.success(`${player === "front" ? "黑子" : "绿子"}赢!`);
};

// 匹配玩家
const matchPlayer = () => {
  // 正在匹配中 禁止点击
  if (loading.value) return;

  board.value = Array(Board_Size * Board_Size).fill("");
  winner.value = "";
  activePlayer.value = "";
  loading.value = true;
  playerRole.value = "";

  if (btnName.value === "匹配玩家") {
    connectServer(); // 发起连接
  } else if (btnName.value === "再玩一局") {
    myWs?.close?.(); // 关闭连接
    connectServer(); // 重新连接
  }

  btnName.value = "正在匹配中...";
};

// 创建连接,并处理服务器推送的消息
const connectServer = () => {
  myWs = new myWebSocket();
  myWs.connect(handleServerMsg);
};

// 处理服务器推送的消息
const handleServerMsg = (data = {}) => {
  const action = {
    board: () => {
      board.value = data.content;
      activePlayer.value = data.role;
      btnName.value = "对战中...";
      if (data.aiPlaceIndex) {
        placeChess(data.aiPlaceIndex);
      }
    },
    role: () => {
      playerRole.value = data.role;
      activePlayer.value = "front";
      loading.value = false;
      setMouseFollowChess();
      btnName.value = "匹配成功";
    },
    winner: () => {
      showWinnerMsg(data.winner);
      winner.value = data.winner;
      btnName.value = "再玩一局";
    },
    disconnect: () => {
      message.error("对方掉线了");
      myWs?.close?.();
      btnName.value = "匹配玩家";
    },
  };

  action?.[data.type]?.();
};
</script>
<style lang="less">
.player-box {
  display: flex;
  align-items: center;
  justify-content: space-between;
  .avatar-box {
    position: relative;
    width: 60px;
    height: 60px;

    &:nth-of-type(1) {
      & > .wait,
      & > .avatar {
        color: v-bind(FrontChessColor);
      }
    }
    &:nth-of-type(2) {
      & > .wait,
      & > .avatar {
        color: v-bind(BackChessColor);
      }
    }

    .wait,
    .avatar {
      position: absolute;
    }

    .wait {
      font-size: 60px;
      z-index: 1;
      display: none;
      &.active {
        display: block;
      }
    }

    .avatar {
      left: 10px;
      top: 7px;
      font-size: 40px;
      z-index: 2;
    }
  }
}

.text-highlight {
  font-size: 18px;
  font-weight: bold;
}

.chess-board {
  display: grid;
  grid-template-columns: repeat(v-bind(Board_Size), 1fr);
  grid-gap: 1px;
  background-color: #d18b47;
  padding: 8px;
  width: fit-content;
  margin: 0 auto;
  .chess-grid {
    width: 30px;
    height: 30px;
    background-color: #f9d16b;
    cursor: pointer;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;

    // 隐藏最后一列和最后一行超出棋盘边界的棋子
    &:nth-child(16n),
    &:nth-child(n + 240) {
      & > .chess-cell {
        display: none;
      }
    }

    & > .chess-cell {
      width: 20px;
      height: 20px;
      border-radius: 50%;
      position: absolute;
      right: -10px;
      bottom: -10px;
      z-index: 1;

      &.front {
        background-color: v-bind(FrontChessColor);
      }

      &.back {
        background-color: v-bind(BackChessColor);
      }
    }
  }
}

.follow-mouse {
  position: absolute;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  /* 防止遮挡鼠标事件 */
  pointer-events: none;
  display: none;
  &.show {
    display: block;
  }
}
</style>
